﻿// Copyright 2012 Jack Bulan (agentgeo)
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using GemWorkshop.Formats.Nitro;
using GemWorkshop.Formats.Nsbmd;
using GemWorkshop.Formats.Nsbtx;

namespace GemWorkshop.Map
{
    public class MapLoader
    {
        //private const string ARM9 = "arm9.bin";
        private string[] mapNames;

        private Narc n, texN;
        private string arm9;
        public uint MovementSize, ObjectSize, NSBMSize, BDHCSize;
        public Movement[,] Movements;
        public bool HasObjects;
        public Object[] Objects;
        public MapHeader Header;
        public string Name;

        public sBMD0 Nsbmd;
        public sBTX0 Texture;

        public MapLoader(Narc mapNarc, Narc texNarc, string arm9)
        {
            n = mapNarc;
            //hNarc = headerNarc;
            texN = texNarc;
            this.arm9 = arm9;
            mapNames = LoadMapNames();
        }

        public void Load(int header, int map)
        {
            LoadHeader(header);
            LoadMap(map);
        }

        private void LoadMap(int map)
        {
            BinaryReader br = new BinaryReader(n.ExtractStream((uint)map));

            MovementSize = br.ReadUInt32();
            ObjectSize = br.ReadUInt32();
            NSBMSize = br.ReadUInt32();
            BDHCSize = br.ReadUInt32();

            LoadMovements(br);
            HasObjects = LoadObjects(br);
            LoadNsbmd(br);
            // Then follows the bdhc section

            br.Close();
            br.Dispose();
        }

        private void LoadMovements(BinaryReader br)
        {
            Movements = new Movement[32, 32]; // Maps are always 32 x 32

            for (int y = 0; y < 32; y++)
            {
                for (int x = 0; x < 32; x++)
                {
                    // Format: [permission] [flag]
                    Movements[x, y].Permission = br.ReadByte();
                    Movements[x, y].Flag = br.ReadByte();
                }
            }

            // Movements are VERY simple, thank goodness.
        }

        private bool LoadObjects(BinaryReader br)
        {
            uint objectCount = ObjectSize / 0x30;
            if (objectCount < 1) return false;

            Objects = new Object[objectCount];
            for (int i = 0; i < objectCount; i++)
            {
                Objects[i].Number = br.ReadInt32();

                Objects[i].XFlag = br.ReadUInt16();
                short y = (short)(br.ReadByte() + (br.ReadByte() << 8));
                Objects[i].X = y > 16 ? (short)(y - 0xFFEF) : (short)(y + 17);
                
                Objects[i].YFlag = br.ReadUInt16();
                Objects[i].Y = br.ReadInt16();
                
                Objects[i].ZFlag = br.ReadUInt16();
                short z = (short)(br.ReadByte() + (br.ReadByte() << 8));
                Objects[i].Z = z > 16 ? (short)(z - 0xFFEF) : (short)(z + 17);

                br.BaseStream.Seek(13L, SeekOrigin.Current);

                Objects[i].Width = br.ReadInt32();
                Objects[i].Height = br.ReadInt32();
                Objects[i].Length = br.ReadInt32();

                br.BaseStream.Seek(7L, SeekOrigin.Current);
            }

            return true;
        }

        private void LoadNsbmd(BinaryReader br)
        {
            byte[] buffer = br.ReadBytes((int)NSBMSize); // So we can work our reader
            Nsbmd = BMD0Reader.Read(new MemoryStream(buffer));
            
            // Now, we need to load the texture...
            sBTX0 btx = BTX0Reader.Read(texN.ExtractFile(Header.Textures), 0);
            Texture = btx;
            //return bmd0;
        }

        private void LoadHeader(int map)
        {
            BinaryReader br = new BinaryReader(File.OpenRead(arm9));
            
            // Headers start at 0xEEDBC.
            // They're 24 bytes long?
            br.BaseStream.Position = 0xEEDBC + (map * 24);
            Header = new Map.MapHeader();
            // Here we go...
            Header.Textures = br.ReadByte();
            Header.ObjectTexutres = br.ReadByte();

            Header.Matrix = br.ReadUInt16();
            Header.Scripts = br.ReadUInt16();
            Header.Unknown1 = br.ReadUInt16();
            Header.Texts = br.ReadUInt16();
            Header.MusicDay = br.ReadUInt16();
            Header.MusicNight = br.ReadUInt16();
            Header.WildPokemon = br.ReadUInt16();
            Header.Events = br.ReadUInt16();
            Header.Name = br.ReadUInt16();

            Header.Weather = br.ReadByte();
            Header.Camera = br.ReadByte();
            Header.NameStyle = br.ReadByte();
            Header.Flags = br.ReadByte();

            Name = GetMapName(map);

            br.Close();
            br.Dispose();
        }

        public string GetMapName(int map)
        {
            return mapNames[map];
        }

        public static string[] LoadMapNames()
        {
            BinaryReader br = new BinaryReader(File.OpenRead(Form1.GameDirectory + "\\" + Form1.GameSettings.ReadValue(Form1.Game, "MapNames")));

            List<string> names = new List<string>();
            while (br.BaseStream.Position < br.BaseStream.Length)
            {
                byte[] name = br.ReadBytes(16);
                string s = "";
                foreach (byte b in name)
                {
                    if (b == 0x00) break;
                    s += (char)b;
                }
                names.Add(s);
            }
            //mapNames = names.ToArray();

            br.Close();
            br.Dispose();
            return names.ToArray();
        }

        public Narc MapData
        {
            get { return n; }
        }

        public Narc TextureData
        {
            get { return texN; }
        }

        public string Arm9
        {
            get { return arm9; }
        }

        #region Export/Import

        public bool ExportNsbmd(string outFile, int map, bool textured = false)
        {
            // NOTE: This expects that we have already loaded the map already!
            try
            {
                BinaryWriter bw = new BinaryWriter(File.Create(outFile));
                BinaryReader br = new BinaryReader(n.ExtractStream((uint)map));

                long start = MovementSize + ObjectSize + 0x10; // header
                br.BaseStream.Seek(start, SeekOrigin.Begin);
                byte[] models = br.ReadBytes((int)NSBMSize);
                bw.Write(models);

                if (textured)
                {
                    bw.Write(texN.ExtractFile(Header.Textures));
                    //bw.Write(texN.ExtractFile(Header.ObjectTexutres));
                }

                br.Close();
                br.Dispose();
                bw.Close();
                bw.Dispose();
            }
            catch (Exception ex)
            {
                return false;
            }
            return true;
        }

        #endregion
    }

    public struct Movement
    {
        public byte Permission, Flag;
    }

    public struct Object
    {
        public int Number;

        public short X, Y, Z;
        public ushort XFlag, YFlag, ZFlag;

        public int Width, Height, Length;
    }

    public struct MapHeader
    {
        public byte Textures, ObjectTexutres;
        public ushort Matrix, Scripts, Unknown1, Texts;
        public ushort MusicDay, MusicNight;
        public ushort WildPokemon;
        public ushort Events, Name;
        public byte Weather, Camera, NameStyle, Flags;

        //public string Name;
    }
}
